import numpy as np

from init import *
from sklearn.decomposition import PCA
from skfuzzy.cluster import cmeans
import copy
from collections import Counter

from init import *
from method import *
import pandas as pd

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False


def get_feature(img_data, flag, ksize):
    '''
    对图像的特征进行提取
    :param img_data: 图像信息
    :param flag: 奇数点还是偶数点
    :return:
    '''
    row, col = img_data.shape
    size = int((row * col) / 2)
    F = np.zeros((ksize, size))
    # 预测点
    predict = np.zeros((row, row))
    # 误差值
    erromap = np.zeros((row, row))
    index = 0
    for i in range(2, row - 2):
        for j in range(2, col - 2):
            # 像素上下左右四个像素
            v1 = int(img_data[i - 1][j])
            v2 = int(img_data[i][j - 1])
            v3 = int(img_data[i + 1][j])
            v4 = int(img_data[i][j + 1])
            # 选中点
            x = int(img_data[i][j])
            # 其他九个点
            v5 = int(img_data[i - 2][j - 1])
            v6 = int(img_data[i - 1][j - 2])
            v7 = int(img_data[i + 1][j - 2])
            v8 = int(img_data[i + 2][j - 1])
            v9 = int(img_data[i + 2][j + 1])
            v10 = int(img_data[i + 1][j + 2])
            v11 = int(img_data[i - 1][j + 2])
            v12 = int(img_data[i - 2][j + 1])
            # 左梯度和右梯度
            lg = np.array([[v5 - v1, v2 - v1], [v1 - v4, v3 - v4], [v2 - v3, v8 - v3], [v6 - v2, v7 - v2]])
            rg = np.array([[v12 - v1, v4 - v1], [v11 - v4, v10 - v4], [v4 - v3, v9 - v3], [v1 - v2, v3 - v2]])
            # # 特征
            f1 = np.max([v1, v2, v3, v4])
            f2 = np.min([v1, v2, v3, v4])
            sod1 = abs(v2 + v4 + v5 + v12 - 4 * v1)
            sod2 = abs(v1 + v3 + v6 + v7 - 4 * v2)
            sod3 = abs(v2 + v4 + v8 + v9 - 4 * v3)
            sod4 = abs(v1 + v3 + v10 + v11 - 4 * v4)
            f3 = np.sum([sod1, sod2, sod3, sod4])
            f4_arr = [abs(v1 - v2), abs(v2 - v3), abs(v3 - v4), abs(v4 - v1)]
            f4 = np.sum(f4_arr)
            # p2 = np.average([abs(v1 - v2), abs(v2 - v3), abs(v3 - v4), abs(v4 - v1)])
            p1 = (math.modf(np.average([v1, v2, v3, v4])))[0]
            p = np.ceil(np.average([v1, v2, v3, v4])) if p1 >= 0.5 else np.floor(np.average([v1, v2, v3, v4]))
            # f5_arr = [p2 - abs(v1 - v2), p2 - abs(v2 - v3), p2 - abs(v3 - v4), p2 - abs(v4 - v1)]
            # f5 = np.average(np.square(f5_arr))
            # f6_arr = [p - v1, p - v2, p - v3, p - v4]
            # f6 = np.average(np.square(f6_arr))
            f5_arr = [abs(v6 - v7), abs(v5 - v2), abs(v2 - v8), abs(v1 - v3), abs(v12 - v4), abs(v4 - v9),
                      abs(v11 - v10), abs(v5 - v12), abs(v6 - v1), abs(v1 - v11), abs(v2 - v4), abs(v7 - v3),
                      abs(v3 - v10), abs(v8 - v9)]
            f5 = np.sum(f5_arr)
            f6_arr = [abs(p - v1), abs(p - v2), abs(p - v3), abs(p - v4), abs(p - v5), abs(p - v6), abs(p - v7),
                      abs(p - v8), abs(p - v9), abs(p - v10), abs(p - v11), abs(p - v12)]
            f6 = np.sum(f6_arr)
            f7 = np.sum(np.sqrt(np.sum(np.square(lg), axis=1)))
            f8 = np.sum(np.sqrt(np.sum(np.square(rg), axis=1)))
            gradDiff = []
            gradDiff2 = []
            for s in range(3):
                for q in range(s, 4):
                    gradDiff.append(lg[s] - lg[q])
                    gradDiff2.append(rg[s] - rg[q])
            # print(gradDiff, gradDiff2)
            gradDiff = np.array(gradDiff)
            gradDiff2 = np.array(gradDiff2)
            f9 = np.sum(np.sqrt(np.sum(np.square(gradDiff), axis=1)))
            f10 = np.sum(np.sqrt(np.sum(np.square(gradDiff2), axis=1)))
            a = np.array([f1, f2, f4, f3, f10, f9, f8, f7, f5, f6])
            if np.mod(i + j, 2) == 0 and flag == 0:
                # print(a)
                F[:, index] = a
                index += 1
                erromap[i][j] = int(img_data[i][j]) - int(p)

            if np.mod(i + j, 2) == 1 and flag == 1:
                F[:, index] = a
                index += 1
                erromap[i][j] = int(img_data[i][j]) - int(p)

    pca = PCA(n_components=3)
    data = pca.fit_transform(F.T)
    data = np.array(data)
    # enter, u, u0, d, jm, p, fpc = cmeans(data.T, m=2, c=16, error=0.5, maxiter=10000)
    center, u, u0, d, jm, p, fpc = cmeans(data.T, 16, 2, error=0.00001, maxiter=100)
    label = np.argmax(u, axis=0)
    print('label', label)
    print(np.unique(label))
    return label, erromap


def complexity_map(img_data, label, flags):
    cm = np.ones(img_data.shape) * 999
    A, B = img_data.shape
    count = 0
    for i in range(2, A - 2):
        for j in range(2, B - 2):
            if np.mod(i + j, 2) == 0 and flags == 0:
                cm[i][j] = int(label[count])
                count = count + 1
            if np.mod(i + j, 2) == 1 and flags == 1:
                cm[i][j] = int(label[count])
                count = count + 1
    return cm



def cal_entropy(imgs, label, ks, flags):
    A, B = imgs.shape
    his = []
    count = 0
    entropy = []
    for i in range(ks):
        his.append([])
    for i in range(2, A - 2):
        for j in range(2, B - 2):
            v1 = int(imgs[i - 1][j])
            v2 = int(imgs[i + 1][j])
            v3 = int(imgs[i][j + 1])
            v4 = int(imgs[i][j - 1])
            p1 = (math.modf(np.average([v1, v2, v3, v4])))[0]
            p = np.ceil(np.average([v1, v2, v3, v4])) if p1 >= 0.5 else np.floor(np.average([v1, v2, v3, v4]))
            e =int(imgs[i][j])- p
            if flags == 0 and np.mod(i + j, 2) == 0:
                his[label[count]].append(e)
                count = count + 1
            elif flags == 1 and np.mod(i + j, 2) == 1:
                his[label[count]].append(e)
                count = count + 1

    for i in range(ks):
        e = 0
        c = Counter(his[i])
        total = len(his[i])
        for i in c.values():
            pro = int(i) / total
            e = e + pro * math.log(1 / pro, 2)
        entropy.append(e)
    print(entropy)
    entropy_sort = sorted(entropy);
    cor = []
    labels = []
    print(entropy_sort)
    for i in range(ks):
        for j in range(ks):
            if entropy[i] == entropy_sort[j]:
                cor.append(j)
                entropy_sort[j] = -1
                break
    print(cor)
    for i in label:
        labels.append(cor[i])
    legend = []
    fig = []
    frequency = list(range(ks))
    new_name = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']
    for i in range(ks):
        legend.append('l=' + str(cor[i]))
        fig.append(str(cor[i]))
        # print(len(his[i]))
        frequency[cor[i]] = int(len(his[cor[i]]))
    plt.grid(ls=':');
    plt.bar(range(ks), frequency, tick_label=new_name, color='b');
    plt.title('The distribution of pixels');
    plt.xlabel('Neuron')
    plt.ylabel('Population');
    plt.show()
    return labels, cor


def cal_aux(checklist, data_length, scale, re, lcm, flags):
    cl = 0
    test = []
    for i in range(16):
        test.append(i)
    checklist = np.array(checklist) + 1
    # checklist = np.array(test)+1
    print('incheck', checklist)
    # a = np.ceil(np.log2(np.max(checklist)))
    if flags == 0:
        for i in range(16):
            #
            if re[i] == 250:
                scale = scale - 1
            if checklist[i] != i:
                cl = cl + np.ceil(np.log2(checklist[i]))
        if np.sum(lcm) == 0:
            aux = scale * 3 + 4 + 1 + 17 + cl + 4
        else:
            aux = scale * 3 + 4 + 1 + data_length * 8 + 17 + 17 + cl + 4
    if flags == 1:  # 根据寻优的bins更新辅助信息的长度
        for i in range(16):
            if re[i] < 250 and checklist[i] > i:
                cl = cl + np.ceil(np.log2(checklist[i]))
            if re[i] == 250:
                scale = scale - 1
                print('scale', scale)
        if np.sum(lcm) == 0:
            aux = scale * 3 + 4 + 1 + 17 + cl + 4
        else:
            aux = scale * 3 + 4 + 1 + data_length * 8 + 17 + 17 + cl + 4
    print('The aux is ', flags, ' ', aux)
    return aux


def re_hs(k, row, col, cm, flag, em):
    Hs = np.zeros((511, k))
    # index = 0
    for i in range(2, row - 2):
        for j in range(2, col - 2):
            if int(cm[i][j]) !=999:
                if np.mod(i + j, 2) == 0 and flag == 0:
                    Hs[int(em[i][j] + 255)][int(cm[i][j])] += 1
                    # index += 1
                if np.mod(i + j, 2) == 1 and flag == 1:
                    Hs[int(em[i][j] + 255)][int(cm[i][j])] += 1
                    # index += 1
    return Hs


def insert_msg(em, cm, bin, img_hide3, secret, ec3, flag3):
    '''
        em误差矩阵
        Dbis复杂度矩阵
        bin最佳区间
        img_hide图形矩阵
        secret嵌入矩阵
        插入需要插入的信息
    '''
    index = 0
    index2 = 0
    row, col = img_hide3.shape
    for i in range(2, row - 2):
        for j in range(2, col - 2):
            if np.mod(i + j, 2) == 0 and flag3 == 0 and ec3 > 0:
                ks = cm[i][j] - 1
                if em[i][j] == bin[int(ks)]:
                    ec3 = ec3 - 1
                    img_hide3[i][j] += secret[i][j]
                    if secret[i][j] == 1:
                        index2 += 1
                if em[i][j] == -bin[int(ks)] - 1:
                    ec3 = ec3 - 1
                    img_hide3[i][j] -= secret[i][j]
                    if secret[i][j] == 1:
                        index2 += 1
                if em[i][j] > bin[int(ks)]:
                    img_hide3[i][j] += 1
                    index += 1
                if em[i][j] < -bin[int(ks)] - 1:
                    img_hide3[i][j] -= 1
                    index += 1
            if np.mod(i + j, 2) == 1 and flag3 == 1 and ec3 > 0:
                ks = cm[i][j] - 1
                if em[i][j] == bin[int(ks)]:
                    ec3 = ec3 - 1
                    img_hide3[i][j] += secret[i][j]
                    if secret[i][j] == 1:
                        index2 += 1
                if em[i][j] == -bin[int(ks)] - 1:
                    ec3 = ec3 - 1
                    img_hide3[i][j] -= secret[i][j]
                    if secret[i][j] == 1:
                        index2 += 1
                if em[i][j] > bin[int(ks)]:
                    img_hide3[i][j] += 1
                    index += 1
                if em[i][j] < -bin[int(ks)] - 1:
                    img_hide3[i][j] -= 1
                    index += 1

    print("偏移数量", index)
    print('偏移点数量', index2)
    return img_hide3, ec3


def instert_ms(em, cm, img_hide3, secret, RateList, flag3, PZlist):
    row, col = img_hide3.shape
    index = 0
    for i in range(2, row - 2):
        for j in range(2, col - 2):
            m = int(cm[i][j])
            types = 0
            if m != 999:
                if RateList[m] != 0:
                    P = PZlist[m][0]
                    Z = PZlist[m][1]
                    types = 1
                else:
                    types = 0
            if np.mod(i + j, 2) == 0 and flag3 == 0 and types == 1:
                for k in range(len(P)):
                    # if em[i][j] == (P[k]-255):
                    #     img_hide3[i][j] +=secret[i][j]
                    #     ec3-=1
                    if P[k] > Z[k] and RateList[m] > 0:
                        # if em[index] == (P[k] - 255):
                        if em[i][j] == (P[k] - 255) and RateList[m] > 0:
                            img_hide3[i][j] -= secret[i][j]
                            RateList[m] -= 1
                        if em[i][j] > (Z[k] - 255) and em[i][j] < (P[k] - 255):
                            # if em[index]  > (Z[k] - 255) and em[index]  < (P[k] - 255):
                            img_hide3[i][j] -= 1
                            # index += 1
                    if P[k] < Z[k] and RateList[m] > 0:
                        if em[i][j] == (P[k] - 255):
                            # if em[index]  == (P[k] - 255):
                            img_hide3[i][j] += secret[i][j]
                            RateList[m] -= 1
                        if em[i][j] < (Z[k] - 255) and em[i][j] > (P[k] - 255):
                            # if em[index] < (Z[k] - 255) and em[index]> (P[k] - 255):
                            img_hide3[i][j] += 1
                index += 1
            if np.mod(i + j, 2) == 1 and flag3 == 1 and types == 1:
                for k in range(len(P)):
                    # if em[i][j] == (P[k] - 255):
                    #     img_hide3[i][j] += secret[i][j]
                    #     ec3 -=1
                    if P[k] > Z[k] and RateList[m] > 0:
                        # if em[index] == (P[k] - 255):
                        if em[i][j] == (P[k] - 255) and RateList[m] > 0:
                            img_hide3[i][j] -= secret[i][j]
                            RateList[m] -= 1
                        if em[i][j] > (Z[k] - 255) and em[i][j] < (P[k] - 255):
                            # if em[index] > (Z[k] - 255) and em[index] < (P[k] - 255):
                            img_hide3[i][j] -= 1
                            # index += 1
                    if P[k] < Z[k] and RateList[m] > 0:
                        if em[i][j] == (P[k] - 255) and RateList[m] > 0:
                            # if em[index] == (P[k] - 255):
                            img_hide3[i][j] += secret[i][j]
                            RateList[m] -= 1
                        if em[i][j] < (Z[k] - 255) and em[i][j] > (P[k] - 255):
                            # if em[index] < (Z[k] - 255) and em[index] > (P[k] - 255):
                            img_hide3[i][j] += 1
                index += 1
    return img_hide3, RateList


def ga_searchbin(H, rate):
    T = 300
    G = 100
    maxL = 25
    V, USE = initpopulation(H, maxL, G)
    R, D, Layer = caculateRD(V, maxL, G, H)

    Best_V, Best_dist, feature = find_best(R, D, G, rate, maxL, V)

    Best_V, D, t_D, features = divide_rate(T, G, V, maxL, H, USE, rate, Best_V, feature, Best_dist)

    S_Rate, S_dist, PzList = Choose_PZ(Best_V, H, maxL)

    return S_Rate, S_dist, PzList


def getModifyMap(PzList):
    PZ = []
    for k in range(len(PzList)):
        if len(PzList[k][0]) != 0:
            P = PzList[k][0]
            Z = PzList[k][1]
            P = np.sort(P)
            Z = np.sort(Z)
            for i in range(len(P)):
                for j in range(len(P)):
                    if P[i] < Z[i]:
                        if P[i] + 1 <= Z[i] - 1:
                            if P[i] < P[j] < Z[i]:
                                P[j] += 1
                                Z[j] += 1
                    else:
                        if P[i] - 1 >= Z[i] + 1:
                            if P[i] > P[j] > Z[i]:
                                P[j] -= 1
                                Z[j] -= 1
            PZ.append([P, Z])
        else:
            PZ.append([[], []])
    return PZ


def danci(danci_img, flags, ks, ecfinals):
    danci_img, lcm, halfsize, data = Over_flow(danci_img, flags)
    # 数组长度
    data_length = len(data)
    # 获取图片的坐标的长宽
    row, col = danci_img.shape
    # 初始图像数据,获取误差以及按照图像的复杂度进行分类
    label, em = get_feature(danci_img, flags, 10)
    label, check = cal_entropy(danci_img, label, ks, flags)
    cm = complexity_map(danci_img, label, flags)
    hs = re_hs(16, row, col, cm, flags, em)
    # check = cal_entropy(hs, ks)
    R = np.zeros(16)
    anx = cal_aux(check, data_length, ks, R, lcm, flags)
    # anx = test[0]
    ec1 =np.ceil( ecfinals / 2)
    print('ec1',ec1)
    hs = hs.T
    S_Rate, S_dist, PzList = ga_searchbin(hs, ec1)
    print('此时最佳',S_Rate)
    print('最佳选点',PzList)
    # PzList = getModifyMap(PzList)
    # # img_hide记录修改过后的图像坐标
    img_hide1 = copy.deepcopy(danci_img)
    # # 嵌入的数据secrt_msg[0][index]
    secret_info = secret_msg(row, col)
    # # 嵌入数据之后的误差图像
    img_hide1, RateList = instert_ms(em, cm, img_hide1, secret_info, S_Rate, flags, PzList)
    # # lsb插入辅助信息
    anx_secret = secret_msg(row, col)
    img_hide1 = Lsb_insert(row, col, anx, img_hide1, flags, anx_secret)
    return img_hide1, RateList


def plt_make(psnr):
    x =  np.linspace(0.01, 0.17, 17)
    y = psnr
    plt.plot(x, y, '-r', label='psnr')
    plt.plot(x, y, '.b')
    plt.title('lena psnr')
    plt.xlabel('ec', color='#1C2833')
    plt.ylabel('psnr', color='#1C2833')
    plt.legend(loc='upper left')
    plt.grid()
    plt.show()
